
""" XML Parsing module for the TAG Engine
The table format for the TAG Engine Attributes are:
CREATE TABLE <object.name>_attributes(
name TEXT,
type TEXT,
defaultvalue TEXT,
required INTEGER
);

Replace <object.name> with the name in the object table.

"""

__author__ = "Shuhao Wu"
__copyright__ = "Copyright 2010, Shuhao Wu, TAG Engine development team"
__license__ = "Apache License 2.0"
__version__ = "trunk"
__email__ = "admin@thekks.net"
__status__ = "Development"

from xml.dom import minidom
import sys
import re
import os
import sqlite3
import logging

from tagengine.commons import *

logger = logging.getLogger('tagengine.parsers.xmlparser')
splitptrn = re.compile(r"[^a-zA-Z0-9 ]+")
FILE_LOCATION = os.path.dirname(os.path.realpath(__file__))


class Value:
    def __init__(self, value):
        self.value = value
        self.prepare()
    
    def __str__(self):
        return str(self.value)
    
    def __repr__(self):
        return repr(self.value)
    
    def prepare(self):
        raise NotImplementedError("Must Be Subclassed")

class Text(Value):
    def prepare(self):
        self.type = "text"

class Number(Value):
    def prepare(self):
        self.type = "number"
        self.value = float(self.value)

class List(Value):
    def prepare(self):
        self.type = "list"
        if self.value:
            self.value = splitptrn.split(self.value)
        else:
            self.value = []

class Varname(List):
    def prepare(self):
        List.prepare(self)
        self.type = "varname"

class Trigger(Value):
    def prepare(self):            
        self.type = "trigger"
        
def wrapValue(value, types):
    for type in types:
        Wrapper = None
        try:
            Wrapper = VALUE_TYPE_MAP.get(type, None)
            if Wrapper == None:
                raise TypeError("Unknown type, %s, detected." % type)
            value = Wrapper(value)
        except (TypeError, ValueError) as e:
            if len(types) == 1:
                raise e
            continue
        else:
            return value   
    
    return Wrapper(value)

VALUE_TYPE_MAP = {"trigger": Trigger, "varname": Varname, "list": List, "number": Number, "text": Text}

class PrimaryObject(dict):
    def setup(self, varname, className):
        self.varname = varname
        self.className = className
        

class Data(dict):
    """ Data class for the parsed xml.
    Top Level Dictionary
        - primaryObjectType : Secondary dictionary for list of primary objects belonging to the type
        Secondary Level Dictionary
            - varname : primaryObject
    """
    def searchForObject(self, varname):
        for primaryObjectType in self:
            if varname in self[primaryObjectType]:
                return self[primaryObjectType][varname]
    

class TAGxMLParser:
    VERSION = 0.2
    def __init__(self, xmlsource, xmlformats=[]):
        self.xmlformats = [os.path.join(FILE_LOCATION, "xmldefault.db")] + xmlformats
        self.format = self.getFormat()
        self.document = self.getDocument(xmlsource)
        self.data = self.getData(self.document)
    
    def getFormat(self):
        """ Gets the format of the xml document given a list of format db in
        self.xmlformats """
        
        # Initializes the format dictionary
        format = {}
        
        # Loop through the xml format filenames listed in the self.xmlformats
        for xmlformat in self.xmlformats:
            # Open the database with sqlite3
            conn = sqlite3.connect(xmlformat)
            c = conn.cursor()
            
            # Get the list of objects from the database.
            c.execute("SELECT name, classname, hasvarname, required FROM objects")
            for row in c:
                # Gets the values from the database
                name, classname, hasvarname, required = row[0], row[1], row[2], row[3]
                
                if not classname:
                    classname = camelCase(name, True)
                
                c1 = conn.cursor()
                # Gets the attributes from the database for the primary object.
                c1.execute("SELECT name, type, defaultvalue, required FROM %s_attributes" % name)
                
                # Store them in a seperate dictionary
                attributes = {}
                for attributeRow in c1:
                    attributes[attributeRow[0]] = {"type": tuple(attributeRow[1].split(";")), "defaultvalue": attributeRow[2], "required" : bool(attributeRow[3])}
                    
                c1.close()
                
                # Add to the master format dictionary
                format[name] = {"classname": classname, "hasvarname": hasvarname, "required": required, "attributes" : attributes}
            
            # Get the version of the XML
            c.execute("SELECT * FROM info LIMIT 1")
            for row in c:
                self.version = float(row[0])
                break
            c.close()
            conn.close()
        
        
        
        return format
    
    def getDocument(self, xmlsource):
        """ Returns the parsed xml from a source path. 
            This checks for the version."""
        f = open(xmlsource)
        xml = minidom.parse(f)
        
        try:
            ver = float(xml.getElementsByTagName("tagdocument")[0].getAttribute("ver"))
            if ver == "":
                raise VersionError("Version of the XML document is not declared.")
            
            if ver != self.version: 
                logger.error("The XML version is %.2f and the accepted version is %.2f. Attemping to parse anyway." % (ver, self.__class__.VERSION))
        except Exception as e:
            logger.critical("XML Parsing has failed while trying to get the tagdocument node and its version.")
            raise e
        
        return xml
        
    def getData(self, document, format=False):
        
        # If a format is not given, try to use the format set to the default.
        if not format:
            format = self.format
            
        # Sets the data
        data = Data()
        
        # Loop through the primary objects defined in the format
        for name in format:
            
            # If the name is not in the data dictionary, initializes a blank one.
            if name not in data:
                data[name] = {}
            
            # Gets the list of primary object nodes.
            listOfPrimaryObjectNodes = document.getElementsByTagName(name)
            # For each node in the list
            for primaryObjectNode in listOfPrimaryObjectNodes:
                # Setup a new dictionary for a primary object
                primaryObject = {}
                
                # Try to get the variable name
                varname = primaryObjectNode.getAttribute("var")
                logger.debug("Varname: %s" % varname)
                
                # Check if varname is required
                if format[name]["hasvarname"] and varname == "":
                    raise VarnameNotFoundError("The %s tag must have a varname" % primaryObjectNode.nodeName)
                elif not format[name]["hasvarname"]:
                    varname = name
                    
                # Get the attributes from the defined format
                attributes = format[name]["attributes"]
                
                for attributeName in attributes:
                    nodes = primaryObjectNode.getElementsByTagName(attributeName)
                    value = None
                    
                    if len(nodes) > 0:                    
                        # This method only sets the value of the node to the first text node.
                        # Clarification: n is a node. The data is stored under n.data
                        for n in nodes[0].childNodes:
                            if n.nodeType == n.TEXT_NODE:
                                value = n.data
                                break
                    
                    
                    if value == None or value == "":
                        logger.info("The '%s' tag has no value in this XML file! Setting default value." % attributeName)
                        primaryObject[attributeName] = wrapValue(format[name]["attributes"][attributeName]["defaultvalue"], format[name]["attributes"][attributeName]["type"])
                    else:
                        primaryObject[attributeName] = wrapValue(value, format[name]["attributes"][attributeName]["type"])
                
                data[name][varname] = primaryObject
            
        return data
